// Thud - a dynamic object-oriented language
// Copyright (C) 2009,2010 David H. Hovemeyer <david.hovemeyer@gmail.com>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

/*
 * Path.cpp
 *
 *  Created on: Jan 7, 2010
 *      Author: dhovemey
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cassert>
#include <cstdlib>
#include <cerrno>
#include <climits>
#include <algorithm>
#include "util/String.h"
#include "util/IOException.h"
#include "util/Path.h"

static bool statFile(const std::string &pathStr, struct stat &buf)
{
	int rc = stat(pathStr.c_str(), &buf);
	return rc == 0;
}

Path::Path(const std::string &pathName)
{
	parse(pathName);
	canonicalize();
}

Path::Path(const Path &base, const std::string &pathName)
{
	Path p(pathName);
	if (p.isAbsolute()) {
		*this = p;
	} else {
		// start with base path
		*this = base;

		// copy all path components from the additional path
		std::copy(p.m_componentList.begin(), p.m_componentList.end(), std::back_inserter(m_componentList));

		// canonicalize (to simplify any "." or ".." components)
		canonicalize();
	}
}

Path::Path(const Path &other)
	: m_filesystemRoot(other.m_filesystemRoot)
	, m_isAbsolute(other.m_isAbsolute)
	, m_componentList(other.m_componentList)
{

}

Path::~Path()
{

}

Path& Path::operator=(const Path &rhs)
{
	if (this != &rhs) {
		m_filesystemRoot = rhs.m_filesystemRoot;
		m_isAbsolute = rhs.m_isAbsolute;
		m_componentList = rhs.m_componentList;
	}
	return *this;
}

const std::string Path::asString() const
{
	std::string pathStr(m_filesystemRoot);
	if (m_isAbsolute) {
		pathStr += '/';
	}
	for (ComponentList::const_iterator i = m_componentList.begin(); i != m_componentList.end(); i++) {
		if (i != m_componentList.begin()) {
			pathStr += "/";
		}
		pathStr += *i;
	}
	return pathStr;
}

void Path::popLastComponent()
{
	if (!m_componentList.empty()) {
		m_componentList.pop_back();
	}
}

bool Path::isDirectory()
{
	struct stat buf;
	if (!statFile(asString(), buf)) {
		return false;
	}
	return buf.st_mode & S_IFDIR;
}

bool Path::isFile()
{
	struct stat buf;
	if (!statFile(asString(), buf)) {
		return false;
	}
	return !(buf.st_mode & S_IFDIR);
}

void Path::makeDirectory()
{
	std::string pathStr = asString();

	// protect any single quotes
	std::string quotedPathStr = String::replace(pathStr, "'", "\\' > /dev/null 2>&1");

	std::string cmd("mkdir -p '" + quotedPathStr + "'");
	int rc = system(cmd.c_str());
	if (rc < 0) {
		throw IOException(L"Could not execute mkdir -p");
	}
	int status = WEXITSTATUS(rc);
	if (status != 0) {
		throw IOException(L"Creation of directory '" + String::fromUtf8(pathStr) + L"' failed");
	}
}

bool Path::isSeparator(char c)
{
	return c == '/';
}

Path Path::getCurrentDirectory()
{
	char buf[PATH_MAX + 1];
	char *s = getcwd(buf, PATH_MAX);
	if (s == 0) {
		throw IOException(L"Could not get current directory", errno);
	}
	return Path(buf);
}

void Path::parse(const std::string &pathName)
{
	// FIXME: handle Unix vs. Winodws paths
	m_filesystemRoot = "";

	m_isAbsolute = pathName.size() > 0 && isSeparator(pathName[0]);

	std::string::size_type pos = 0;
	while (true) {
		// skip separators
		while (pos < pathName.size() && isSeparator(pathName[pos])) {
			pos++;
		}

		if (pos >= pathName.size()) {
			break;
		}

		// extract one component (until next separator, or end of string)
		std::string component;
		while (pos < pathName.size() && !isSeparator(pathName[pos])) {
			component += pathName[pos];
			pos++;
		}
		assert(component.size() > 0);
		m_componentList.push_back(component);
	}

}

void Path::canonicalize()
{
	ComponentList stack;
	for (ComponentList::iterator i = m_componentList.begin(); i != m_componentList.end(); i++) {
		if (*i == ".") {
			continue;
		}
		if (*i == ".." && stack.size() > 0) {
			stack.pop_back();
			continue;
		}
		stack.push_back(*i);
	}

	if (!m_isAbsolute && stack.empty()) {
		// A completely empty path is assumed to refer to the current directory
		stack.push_back(".");
	}

	m_componentList = stack;
}
